#include <pybind11/pybind11.h>
#include "vall.h"
#include "{{dut_name}}.h"

namespace py = pybind11;

#define INVAL_UINT   123456789           // Magic value
#define INVAL_INT    -12345678           // Magic value
#define INVAL_UINT64 12345678987654321   // Magic value
#define INVAL_BOOL   -1                  // 0 and >0 is validate


#define MCV_DEF_VlWide(n) py::class_<VlWide<n>>(m, "VlWide_#n") \
        .def(py::init<>())\
        .def("__getitem__", [](VlWide<3>*self, int index){\
            return (*self)[index];\
        })\
        .def("__setitem__", [](VlWide<3>*self, int index, uint32_t value){\
            return (*self)[index] = value;\
        });

#define MCV_DEF_VerilatedTraceConfig py::class_<VerilatedTraceConfig>(m, "VerilatedTraceConfig") \
        .def(py::init<bool,bool,bool>());

#define MCV_DEF_VerilatedVcdC py::class_<VerilatedVcdFile>(m, "VerilatedVcdFile") \
        .def(py::init<>()); \
        py::class_<VerilatedVcdC>(m, "VerilatedVcdC")\
        .def(py::init([](VerilatedVcdFile *f = nullptr){\
            return new VerilatedVcdC(f);\
        }), py::arg("f")=nullptr)\
        .def("isOpen", &VerilatedVcdC::isOpen)\
        .def("openNext", &VerilatedVcdC::openNext, py::arg("incFilename ")=true)\
        .def("rolloverSize", &VerilatedVcdC::rolloverSize)\
        .def("flush", &VerilatedVcdC::flush)\
        .def("set_time_unit", [](VerilatedVcdC*self, std::string unit){\
            return self->set_time_unit(unit);\
        })\
        .def("set_time_resolution", [](VerilatedVcdC*self, std::string unit){\
            return self->set_time_resolution(unit);\
        })\
        .def("dumpvars", [](VerilatedVcdC*self, int level){\
            std::string hier;\
            self->dumpvars(level, hier);\
            return hier;\
        })\
        .def("dump", [](VerilatedVcdC*self, uint64_t timeui){\
            return self->dump(timeui);\
        })\
        .def("open", [](VerilatedVcdC*self, std::string filename){\
            return self->open(filename.c_str());\
        })\
        .def("close", &VerilatedVcdC::close);

// Util functions
inline std::string str_trim(std::string s)
{
    if (s.empty())
    {
        return s;
    }
    s.erase(0, s.find_first_not_of(" "));
    s.erase(s.find_last_not_of(" ") + 1);
    s.erase(0, s.find_first_not_of("\r"));
    s.erase(s.find_last_not_of("\r") + 1);
    s.erase(0, s.find_first_not_of("\n"));
    s.erase(s.find_last_not_of("\n") + 1);
    return s;
}


inline std::vector<std::string> str_split(std::string str, std::string s = " ")
{
    std::vector<std::string> ret;
    int start = 0;
    int end = str.find(s);
    while (end != -1)
    {
        auto sub = str.substr(start, end - start);
        str_trim(sub);
        ret.push_back(sub);
        start = end + s.size();
        end = str.find(s, start);
    }
    auto sub = str.substr(start, end - start);
    str_trim(sub);
    ret.push_back(sub);
    return ret;
}


void commandArgs(VerilatedContext &self, std::string kwargs=""){
    auto args = str_split(kwargs);
    std::vector<char *> cstrs;
    cstrs.reserve(args.size());
    for (auto &s : args) cstrs.push_back(const_cast<char *>(str_trim(s).c_str()));
    return self.commandArgs(int(cstrs.size()), cstrs.data());
}


// Wrapper class
class MCVWrapper{
    public:
    {{dut_name}}* dut = nullptr;
    MCVWrapper(VerilatedContext* ctx, std::string top){
        this->dut = new {{dut_name}}{ctx, top.c_str()};
    }
    ~MCVWrapper(){
        delete dut;
    }
    // Generated METHODS
    {{mcv_wrapper_funcs}}
    // Generated Attribus
    {{mcv_wrapper_getset}}
};


PYBIND11_MODULE({{mode_name}}, m) {
    m.doc() = "Model: {{mode_name}}. Generated by tool Multi-language-based Chip Verification (MCV)"
              "Version: {{mcv_version}}, Gen Time: {{mcv_time}} with verilator: {{verilator_version}}";

    // Verilator static data definations
    // Version: 4.226 2022-08-31 rev UNKNOWN.REV
    // 0. Functions. all functions star with v_
    m.def("v_mkdir", &Verilated::mkdir);
    
    // 1. VerilatedContext
    py::class_<VerilatedContext>(m, "VerilatedContext")
        .def(py::init<>())
        .def("commandArgs", &commandArgs, py::arg("kwargs") = "")
        .def("debug", [](VerilatedContext* self, int v = INVAL_INT){
            if (v != INVAL_INT){
                self->debug(v);
            }
            return self->debug();
        }, py::arg("v") = INVAL_INT)
        .def("randReset", [](VerilatedContext* self, int v = INVAL_INT){
            if (v != INVAL_INT){
                self->randReset(v);
            }
            return self->randReset();
        }, py::arg("v") = INVAL_INT)
        .def("randSeed", [](VerilatedContext* self, int v = INVAL_INT){
            if (v != INVAL_INT){
                self->randSeed(v);
            }
            return self->randSeed();
        }, py::arg("v") = INVAL_INT)
        .def("assertOn", [](VerilatedContext* self, int flag = INVAL_BOOL){
            if (flag != INVAL_BOOL){
                self->assertOn(flag > 0);
            }
            return self->assertOn();
        }, py::arg("flag") = INVAL_BOOL)
        .def("calcUnusedSigs", [](VerilatedContext* self, int flag = INVAL_BOOL){
            if (flag != INVAL_BOOL){
                self->calcUnusedSigs(flag > 0);
            }
            return self->calcUnusedSigs();
        }, py::arg("flag") = INVAL_BOOL)
        .def("traceEverOn", &VerilatedContext::traceEverOn)
        .def("timeInc", &VerilatedContext::timeInc)
        .def("internalsDump", &VerilatedContext::internalsDump)
        .def("scopesDump", &VerilatedContext::scopesDump)
        .def("dumpfileCheck", &VerilatedContext::dumpfileCheck)
        .def("dumpfile", [](VerilatedContext* self){return self->dumpfile();})
        .def("time", [](VerilatedContext* self, uint64_t v = INVAL_UINT64){
            if (v != INVAL_UINT64){
                self->time(v);
            }
            return self->time();
        }, py::arg("v") = INVAL_UINT64)
        .def("timeunit", [](VerilatedContext* self, int v = INVAL_INT){
            if (v != INVAL_INT){
                self->timeunit(v);
            }
            return self->timeunit();
        }, py::arg("v") = INVAL_INT)
        .def("timeprecision", [](VerilatedContext* self, int v = INVAL_INT){
            if (v != INVAL_INT){
                self->timeprecision(v);
            }
            return self->timeprecision();
        }, py::arg("v") = INVAL_INT)
        .def("timeunitString", [](VerilatedContext*self){return std::string(self->timeunitString());})
        .def("timeprecisionString", [](VerilatedContext*self){return std::string(self->timeprecisionString());})
        .def("threads", [](VerilatedContext* self, unsigned int v = INVAL_UINT){
            if (v != INVAL_UINT){
                self->threads(v);
            }
            return self->threads();
        }, py::arg("v") = INVAL_UINT)
        .def("coveragep_write", [](VerilatedContext*self, std::string fname){
            #if VM_COVERAGE
                self->coveragep()->write(fname.c_str());
            #endif
        })
        .def("commandArgsPlusMatch", [](VerilatedContext*self, std::string prefixp){
            return std::string(self->commandArgsPlusMatch(prefixp.c_str()));
        })
        .def("coveragep", [](VerilatedContext*self){
            #if VM_COVERAGE
                return self->coveragep();
            #endif
        })
        .def("errorCountInc", &VerilatedContext::errorCountInc)
        .def("errorCount", [](VerilatedContext* self, int v = INVAL_INT){
            if (v != INVAL_INT){
                self->errorCount(v);
            }
            return self->errorCount();
        }, py::arg("v") = INVAL_INT)
        .def("errorLimit", [](VerilatedContext* self, int v = INVAL_INT){
            if (v != INVAL_INT){
                self->errorLimit(v);
            }
            return self->errorLimit();
        }, py::arg("v") = INVAL_INT)
        .def("fatalOnError", [](VerilatedContext* self, int flag = INVAL_BOOL){
            if (flag != INVAL_BOOL){
                self->fatalOnError(flag > 0);
            }
            return self->fatalOnError();
        }, py::arg("flag") = INVAL_BOOL)
        .def("fatalOnVpiError", [](VerilatedContext* self, int flag = INVAL_BOOL){
            if (flag != INVAL_BOOL){
                self->fatalOnVpiError(flag > 0);
            }
            return self->fatalOnVpiError();
        }, py::arg("flag") = INVAL_BOOL)
        .def("gotError", [](VerilatedContext* self, int flag = INVAL_BOOL){
            if (flag != INVAL_BOOL){
                self->gotError(flag > 0);
            }
            return self->gotError();
        }, py::arg("flag") = INVAL_BOOL)
        .def("gotFinish", [](VerilatedContext* self, int v = INVAL_BOOL){
            if (v != INVAL_BOOL){
                self->gotFinish(v > 0);
            }
            return self->gotFinish();
        }, py::arg("v") = INVAL_BOOL).
        def("if_compile_trace", [](VerilatedContext*self){
            #if VM_TRACE
            return true;
            #else
            return false;
            #endif
        });
    
    // 2. VerilatedCovContext
    #if VM_COVERAGE
    py::class_<VerilatedCovContext,std::unique_ptr<VerilatedCovContext, py::nodelete>>(m, "VerilatedCovContext")
        .def("defaultFilename", [](VerilatedCovContext*self){
            return std::string(self->defaultFilename());
        })
        .def("forcePerInstance", &VerilatedCovContext::forcePerInstance)
        .def("clear", &VerilatedCovContext::clear)
        .def("zero", &VerilatedCovContext::zero)
        .def("write", [](VerilatedCovContext*self, std::string fname){
            if(fname.empty()){
                fname = std::string(self->defaultFilename());
            }
            return self->write(fname.c_str());
        }, py::arg("fname")="")
        .def("clearNonMatch", [](VerilatedCovContext*self, std::string matchp){
            return self->clearNonMatch(matchp.c_str());
        });
    #endif

    // used data
    {{template_data_define}}

    // Generated from UDT
    py::class_<MCVWrapper>(m, "{{dut_name}}")
        .def(py::init([](VerilatedContext* ctx, std::string top){
            return new MCVWrapper(ctx, top);
        }), py::arg("ctx")=nullptr, py::arg("top")="top")
        {{mcv_dut_getset}}
        {{mcv_dut_funcs}};
}
